/*
	The Shadow Framework - a complete framework for Real Time Graphics based on OpenGL
    Copyright (C) 2008  Alessandro Martinelli  <alessandro.martinelli@unipv.it>

    This file is part of The Shadow Framework.

    The Shadow Framework is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    The Shadow Framework is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Shadow Framework.  If not, see <http://www.gnu.org/licenses/>.
*/

package objLoader.math;


public class PolinomialFunction2D {

	private float coeffs[];
	private int level;
	
	public PolinomialFunction2D(int n){
		level=n;
		coeffs=new float[((n+1)*(n+2))/2];
	}
	
	public void setValue(int n1,int n2, float value){
		
		int n=n1+n2;
		n=(((n)*(n+1))/2)+(n2);
		
		if(n<coeffs.length)
			coeffs[n]=value;
	}
	
	public int getNumberOfCoeffs(){
		return ((level+1)*(level+2))/2;
	}
	
	public float getValue(int n1,int n2){

		int n=n1+n2;
		n=(((n)*(n+1))/2)+(n2);
		
		if(n<coeffs.length){
			return coeffs[n];
		}
		
		return 0;
	}
	
	public void setValue(int n, float value){
		
		if(n<coeffs.length)
			coeffs[n]=value;
	}
	
	public float getValue(int n){
		
		if(n<coeffs.length){
			return coeffs[n];
		}
		
		return 0;
	}
	
	public void toZero(){
		
		int n=coeffs.length;
		
		for(int i=0;i<n;i++){
			coeffs[i]=0;
		}
	}
	
	public boolean sum(PolinomialFunction2D function,float a,float b){
		
		int n=level;
		if(function.level>level)
			return false;
		
		n=coeffs.length;
		
		for(int i=0;i<n;i++){
			coeffs[i]=coeffs[i]*a+function.getValue(i)*b;
		}
		
		return true;
	}
	
	public PolinomialFunction2D getSum(PolinomialFunction2D function,float a,float b){
	
		int n=level;
		if(function.level>level)
			n=function.level;
		
		PolinomialFunction2D ret=new PolinomialFunction2D(n);
		
		n=ret.coeffs.length;
		
		for(int i=0;i<n;i++){
			ret.coeffs[i]=getValue(i)*a+function.getValue(i)*b;
		}
		
		return ret;
	}
	
	public PolinomialFunction2D getScalarProduct(float a){
		
		int n=level;
		
		PolinomialFunction2D ret=new PolinomialFunction2D(n);
		
		n=ret.coeffs.length;
		
		for(int i=0;i<n;i++){
			ret.coeffs[i]=getValue(i)*a;
		}
		
		return ret;
	}
	
	public PolinomialFunction2D getProduct(PolinomialFunction2D function){
		
		int n=level+function.level;
		
		PolinomialFunction2D ret=new PolinomialFunction2D(n);
		
		int level2=function.level;
		
		for(int j=0;j<=level;j++){// per level =3 mi fa 0,1,2,3
			for(int k=0;k<=j;k++){//ad esempio, per j=2 mi fa 0 1 2
				int ns1=k;
				//int nt1=j-k;
				
				for(int i=0;i<=level2;i++){// per level =3 mi fa 0,1,2,3
					for(int l=0;l<=i;l++){//ad esempio, per i=2 mi fa 0 1 2
						int ns2=l;
						//int nt2=i-l;
						
						int n1=((j*(j+1))/2)+k;
						int n2=((i*(i+1))/2)+l;
		
						n=(((i+j)*(i+j+1))/2)+ns1+ns2;
						
						ret.coeffs[n]+=function.coeffs[n2]*coeffs[n1];		
					}
				}
			}
		}
		
		return ret;
	}
	
	
	public PolinomialFunction2D power(int exp){
		
		PolinomialFunction2D temp=new PolinomialFunction2D(0);
		
		temp.coeffs[0]=1;
		
		for(int i=0;i<exp;i++){
			temp=temp.getProduct(this);
		}
		
		return temp;
	}
	
	
	public PolinomialFunction2D getDomainTransformed(Transform2D transform2D){
		
		PolinomialFunction2D ret=new PolinomialFunction2D(level);
		
		PolinomialFunction2D sFunction=new PolinomialFunction2D(1);
		PolinomialFunction2D tFunction=new PolinomialFunction2D(1);
		
		
		sFunction.coeffs[1]=transform2D.getA();
		sFunction.coeffs[2]=transform2D.getB();
		sFunction.coeffs[0]=transform2D.getE();
		tFunction.coeffs[1]=transform2D.getC();
		tFunction.coeffs[2]=transform2D.getD();
		tFunction.coeffs[0]=transform2D.getF();
		
		PolinomialFunction2D sProducts[]=new PolinomialFunction2D[level+1];
		PolinomialFunction2D tProducts[]=new PolinomialFunction2D[level+1];
		
		sProducts[0]=new PolinomialFunction2D(0);
		sProducts[0].coeffs[0]=1;
		tProducts[0]=new PolinomialFunction2D(0);
		tProducts[0].coeffs[0]=1;
		sProducts[1]=sFunction;
		tProducts[1]=tFunction;
		
		//System.out.println("level "+level);
		for(int i=2;i<=level;i++){
			sProducts[i]=sProducts[i-1].getProduct(sFunction);
			tProducts[i]=tProducts[i-1].getProduct(tFunction);
		}

		for(int i=0;i<=level;i++){
			int n=(i*(i+1))/2;
	
			for(int j=0;j<=i;j++){
				if(coeffs[n+j]!=0){
					PolinomialFunction2D temp=sProducts[i-j].getProduct(tProducts[j]);
					
					ret=ret.getSum(temp,1,coeffs[n+j]);
				}
			}
		}
		
		return ret;
	}
	
	public float eval(float s,float t){

		float sum=0;
		
		float ss[]=new float[level+1];
		float ts[]=new float[level+1];
		
		ss[0]=1;
		ts[0]=1;
		
		sum+=coeffs[0];
		
		for(int i=1;i<=level;i++){
			ss[i]=ss[i-1]*s;
			ts[i]=ts[i-1]*t;
			
			int n=(i*(i+1))/2;
			
			for(int j=0;j<=i;j++){
				sum=sum+coeffs[n+j]*ss[i-j]*ts[j];
			}
		}
		
		return sum;
	}
	
	public PolinomialFunction2D getdFds(){

		PolinomialFunction2D function=new PolinomialFunction2D(level-1);
				
		for(int i=1;i<=level;i++){
			
			int n=(i*(i+1))/2;
			int n1=((i-1)*(i))/2;
			
			for(int j=0;j<i;j++){
				int ns=i-j;
				//int nt=j;
				
				function.coeffs[n1+j]=coeffs[n+j]*ns;
			}
		}
		
		return function;
	}
	
	
	public static PolinomialFunction2D[] getNormalOfSurface(PolinomialFunction2D x,
			PolinomialFunction2D y,PolinomialFunction2D z){
	
		PolinomialFunction2D[] n=new PolinomialFunction2D[3];
		
		n[0]=(y.getdFds().getProduct(z.getdFdt())).getSum((y.getdFdt().getProduct(z.getdFds())),1,-1);
		n[1]=(z.getdFds().getProduct(x.getdFdt())).getSum((z.getdFdt().getProduct(x.getdFds())),1,-1);
		n[2]=(x.getdFds().getProduct(y.getdFdt())).getSum((x.getdFdt().getProduct(y.getdFds())),1,-1);
		
		return n;
	}
	
	
	public PolinomialFunction2D getdFdt(){

		PolinomialFunction2D function=new PolinomialFunction2D(level-1);
				
		for(int i=1;i<=level;i++){
			
			int n=(i*(i+1))/2;
			int n1=((i-1)*(i))/2;
			
			for(int j=1;j<=i;j++){
				//int ns=i-j;
				int nt=j;
				
				function.coeffs[n1+j-1]=coeffs[n+j]*nt;
			}
		}
		
		return function;
	}
	
	public String toString(){

		String ret="";
		
		for(int i=0;i<=level;i++){
			
			int n=(i*(i+1))/2;
			
			for(int j=0;j<=i;j++){
				
				if(coeffs[n+j]!=0){
					ret+=" + "+coeffs[n+j];
					if(j!=i)
						ret+="s"+(i-j);
					if(j!=0)
						ret+="t"+(j);
				}				
			}
		}
		
		return ret;
	}


	private static float[][] coeffsOfBeziers={{1}};
	//private static float[][][] coeffsOfInversas={{{1}}};
	
	
	private static void needBezierLevel(int level){
		
		if(coeffsOfBeziers.length+1<level){
			
			float[][] temp1=new float[level+1][];
			
			for(int i=0;i<coeffsOfBeziers.length;i++){
				temp1[i]=coeffsOfBeziers[i];
			}
			
			for(int i=coeffsOfBeziers.length;i<temp1.length;i++){
					
				 float f[]=new float[i+1];
				 
				 f[0]=1;
				 f[i]=1;
				 for(int j=1;j<i;j++){
					 f[j]=temp1[i-1][j]+temp1[i-1][j-1];
				 }
				 
				 temp1[i]=f;
			}
			coeffsOfBeziers=temp1;
		}
		
	}
	
	public static void main(String args[]){
		needBezierLevel(20);
		for(int i=0;i<coeffsOfBeziers.length;i++){
			for(int j=0;j<coeffsOfBeziers[i].length;j++){
				System.out.print(coeffsOfBeziers[i][j]+"  ");
			}
			System.out.println();
		}
	}
}
